前面兩篇我們讓區塊吃屬性、綁資料。今天處理一個幾乎每個網站都要的東西:文章列表。首頁的「最新消息」、部落格的彙整頁、作品集的作品牆,本質都是依條件撈出一批文章,逐篇套同一個版面顯示。
傳統主題這叫 The Loop,要寫 WP_Query、while (have_posts()),Block Theme 有個現成的核心區塊做這件事:Query Loop。
先建立對照。傳統主題的封存頁 archive.php 大概是:
<?php while ( have_posts() ) : the_post(); ?>
<article>
<h2><?php the_title(); ?></h2>
<?php the_excerpt(); ?>
</article>
<?php endwhile; ?>
一個查詢就是一個迴圈,迴圈內是每篇的版面內容,Query Loop 區塊做的是同一件事,只是把它變成可以在編輯器裡拖拉的區塊:外層 wp:query 是查詢(撈哪些文章),裡面的 wp:post-template 是「每篇要套的版面」,你在裡面放 wp:post-title、wp:post-excerpt、wp:post-featured-image,就等於在寫迴圈內的組成。
它的區塊標記像這樣:
<!-- wp:query {"query":{"perPage":6,"postType":"post","order":"desc","orderBy":"date"}} -->
<!-- wp:post-template -->
<!-- wp:post-featured-image /-->
<!-- wp:post-title {"isLink":true} /-->
<!-- wp:post-excerpt /-->
<!-- /wp:post-template -->
<!-- wp:query-pagination /-->
<!-- /wp:query -->
query 裡的 perPage、postType、orderBy 就是以前傳給 WP_Query 的參數;post-template 內那三個區塊,就是迴圈內每篇的排版,連分頁都有現成的 query-pagination,不用自行處理 paginate_links()。
Query Loop 有個容易搞混但很重要的設定:它可以「繼承模板的查詢」或「自訂查詢」。
archive.php 裡直接用主迴圈。new WP_Query() 開一個自訂查詢。如果搞錯的話像是在首頁想顯示最新文章卻用了「繼承」,結果什麼都沒出來(首頁沒有文章主查詢),或在封存頁用了「自訂」,結果分類封存頁不管點哪個分類都顯示一樣的文章。判斷的方法是這個列表要不要跟著當前頁面的情境變?
Query Loop 手刻其實有點繁瑣:一層層區塊設定藏在不同面板,這正適合交給 AI,我會說「在首頁做一個區塊,用 Query Loop 撈最新 6 篇文章,每篇顯示精選圖片、標題(連結)、摘要,三欄呈現,記得用自訂查詢不要繼承」,Claude Code 會把上面那段 wp:query 標記生出來,直接貼進 front-page.html 就能用。
但有兩件事你得會驗:一是繼承 vs 自訂有沒有設對(最常出錯);二是版面有沒有吃 theme.json:每篇卡片的間距、標題字體,應該來自設計元素,而不是 AI 隨手塞的行內樣式。
Query Loop 很強,但它是內建的核心區塊有固定的組成,如果你要的列表版面很特別,例如每張卡片要顯示自訂欄位、要複雜的 hover 互動,用核心區塊組會綁手綁腳。這時回到之前介紹過的動態區塊,自己寫一個 render.php 跑 WP_Query反而更乾淨。
我做了一個相關文章 related-posts 區塊,一個 count 屬性讓你設要幾篇,render.php 直接開一支副查詢,把最新文章撈出來,套進主題自己的卡片版面,具體效果如下:

這一個區塊的程式碼結構如下:
blocks/related-posts/
├── block.json # 宣告 count 屬性、指向 render.php
├── index.js # 編輯器端:RangeControl 調篇數 + ServerSideRender 預覽
├── render.php # 動態渲染:跑 WP_Query 撈文章、套進卡片版面
└── style.css # 卡片樣式(值都吃 theme.json 代幣)
這邊示範在區塊裡面使用 WP_Query 的寫法:
$count = ( isset( $attributes['count'] ) ) ? (int) $attributes['count'] : 3;
$related_query = new WP_Query(
array(
'post_type' => 'post',
'posts_per_page' => $count,
'post__not_in' => array( get_the_ID() ), // 排除當前這篇.
)
);
if ( ! $related_query->have_posts() ) {
return; // 沒文章就不輸出.
}
?>
<div <?php echo get_block_wrapper_attributes(); ?>>
<?php while ( $related_query->have_posts() ) : $related_query->the_post(); ?>
<a class="...__item" href="<?php echo esc_url( get_permalink() ); ?>">
<h3><?php echo esc_html( get_the_title() ); ?></h3>
</a>
<?php endwhile; ?>
<?php wp_reset_postdata(); // 副查詢跑完要重設,免得弄亂後面的主迴圈. ?>
</div>
跟 Query Loop 的差別在 Query Loop 是用核心區塊「拼」出列表,版面受核心區塊框架的限制;related-posts 是自己 new WP_Query()、自己跑 have_posts() 迴圈、自己寫每張卡片的 HTML,版面 100% 你說了算。
代價是多寫一點 PHP,還要記得 wp_reset_postdata() 把主迴圈的狀態還原。這就是「精準掌控」的意思。兩條路並存,看你要的是「快速拼裝」還是「精準掌控」。
到目前為止,我們的自訂區塊都還是「扁平」的,一個區塊就一組固定內容,但有時你會希望自訂區塊能「裝進別的區塊」,像一個能塞任何東西的容器,這就是下一篇的主題:InnerBlocks。
文章目錄:https://oberonlai.blog/category/2026-ithome/